#!/usr/bin/python2.5
# -*- coding: utf-8 -*-

"""
 JooScript parser just extends JavaScript with some new constructions.
 
 Adds:

  * package
  * class
  * let replaces var, that is keyword var is deprecated
  * virtual namespaces
  * null is deprecated, undefined is deprecated, instead null object is "none"

 Created Jan 2011 by Evgen Burzak <buzzilo@gmail.com>
"""

import defs, jsparser

JSParser = jsparser.JSParser
Tokenizer = jsparser.Tokenizer
JSFunction = jsparser.JSFunction
Node = jsparser.Node

defs.map(globals())

# FIXME && and || raises syntax error

class JooParser(JSParser): 
    """
    Parse some JooScript code
    """
    def __init__(self, tz):
        JSParser.__init__(self, tz)
        self.clsDecls = []

    def parse(self):
        n = JSParser.parse(self)
        n.clsDecls = self.clsDecls
        return n

    # >>> let foo Int = 123
    def identifierDefinition(self, *args):
        self.tz.mustMatch(IDENTIFIER)
        return self.identifierExpression()

    # >>> bar String = "abc", baz Bool = true
    # TODO make tests, I'm not sure this hack will works right in all cases ...
    def identifierExpression(self, *args):
        n = Node(self.tz)
        tt = self.tz.peek()
        varType = None
        if tt == IDENTIFIER:
            self.tz.get()
            varType = self.tz.token.value

        n.varType = varType
        return n

    def SelfExpression(self, *args):
        return Node(self.tz)

    # FIXME unique var id for each code block
    def Variables(self, *args):
        tt = self.tz.token.type_;
        if tt in (CONST, VAR):
            self.tz.newSyntaxError("Const and var are deprecated to conform Python syntax")
        return StaticContext.Variables(self, *args)

    def functionDefinition(self, functionForm, requireName=False):
        """
        * Lambda function. Simple rule: if a function has no name, func body must be 
          a single expression.
        """
        f = Node(self.tz)

        if self.tz.match(IDENTIFIER):
            f.name = self.tz.token.value
        elif requireName:
            raise self.tz.newSyntaxError("Missing function identifier")

        self.tz.mustMatch(LEFT_PAREN)
        f.params = []
        while True:
            tt = self.tz.get()
            if tt == RIGHT_PAREN: break
            if tt != IDENTIFIER:
                raise self.tz.newSyntaxError("Missing formal parameter")
            f.params.append(self.tz.token.value)
            if self.tz.peek() != RIGHT_PAREN:
                self.tz.mustMatch(COMMA)

        return self.funcRest(f, functionForm)

    LAMBDA_FORM = 3

    def funcRest(self, f, functionForm):
        fun_ctx = JooFunction(self)
        if functionForm == self.LAMBDA_FORM:
            if self.tz.peek() == RETURN: 
                raise t.newSyntaxError("Invalid return")
            f.body = fun_ctx.expression()
        else:
            self.tz.mustMatch(LEFT_CURLY)
            f.body = fun_ctx.parse()
            self.tz.mustMatch(RIGHT_CURLY)

        f.end = self.tz.token.end

        f.functionForm = functionForm
        if functionForm == self.DECLARED_FORM:
            self.funDecls.append(f)

        return f

    def packageDefinition(self, *args):
        p = Node(self.tz)
        p.group = []
        # FIXME package a.b.c. { 
        while True:
            tt = self.tz.get()
            if tt == LEFT_CURLY: break
            if tt != IDENTIFIER:
                raise self.tz.newSyntaxError("Missing package group")
            p.group.append(self.tz.token.value)
            if self.tz.peek() != LEFT_CURLY:
                self.tz.mustMatch(DOT)

        self.tz.unget()
        self.tz.mustMatch(LEFT_CURLY)
        pack_ctx = JooPack(self)
        p.body = pack_ctx.parse()
        self.tz.mustMatch(RIGHT_CURLY)
        p.end = self.tz.token.end

        return p

    def classDefinition(self, classForm, requireName=False):
        c = Node(self.tz)
        if self.tz.match(IDENTIFIER):
            c.name = self.tz.token.value
        elif requireName:
            raise self.tz.newSyntaxError("Missing class identifier")

        tt = self.tz.peek()
        if tt == AS:
            self.tz.get()
            tt = self.tz.get()
            if tt is not IDENTIFIER:
                raise self.tz.newSyntaxError("Missing class role")
            c.role = self.tz.token.value

        self.tz.mustMatch(LEFT_PAREN)

        # parse ancestors as list
        if self.tz.peek() == RIGHT_PAREN:
            c.ancestors = None
        else:
            c.ancestors = self.expression(RIGHT_PAREN)

        self.tz.get() # pass right paren

        self.tz.mustMatch(LEFT_CURLY)
        cls_ctx = JooClass(self)
        c.body = cls_ctx.parse()
        self.tz.mustMatch(RIGHT_CURLY)
        c.end = self.tz.token.end

        c.classForm = classForm
        self.clsDecls.append(c)
        return c

    # TODO for x in range([start], stop[, step]) ...
    def ForinDefinition(self): pass
        
    def ForDefinition(self):
        self.tz.newSyntaxError("For statement is deprecated to conform Python syntax")

    def SwitchDefinition(self):
        self.tz.newSyntaxError("Switch statement is deprecated to conform Python syntax")

    def ConditionalExpression(self):
        n = self.OrExpression()
        if self.tz.match(HOOK):
            self.tz.newSyntaxError("Hook expression is deprecated to conform Python syntax")
        return n

    def UnaryExpression(self):
        tt = self.tz.peek(True)
        if tt in (INCREMENT, DECREMENT):
            self.tz.newSyntaxError("Increment and decrement is deprecated to conform Python syntax")
        else:
            return self.UnaryExpression()

    def MemberExpression(self, allowCallSyntax) :
        if self.tz.match(NEW) :
            self.tz.newSyntaxError("New constructor is deprecated to conform Python syntax")
        else:
            return self.MemberExpression(allowCallSyntax)

    def PrimaryExpression(self):
        if tt in (NULL, UNDEFINED):
            self.tz.newSyntaxError("Null object is 'none'")

    def classExpression(self, *args):
        return self.classDefinition(self.EXPRESSED_FORM, requireName=False)

    def importDefinition(self, *args):
        n = Node(self.tz)
        n.groups = []
        group = []
        while True:
            tt = self.tz.get()
            if tt != IDENTIFIER:
                if tt == DOT and len(group) == 0:
                    raise self.tz.newSyntaxError("Missing group to import")
            if tt == SEMICOLON: break
            elif tt in (IDENTIFIER, MUL):
                group.append(self.tz.token.value)
            elif tt == DOT:
                pass
            elif tt == COMMA:
                n.groups.append(group)
                group = []

        if len(group):
            n.groups.append(group)

        return n

    # FIXME any expression can be defined within it's own namespace?
    def virtualNamespace(self, *args):
        ns = []
        tt = self.tz.token.type_
        while True:
            if defs.tokens[tt].lower() not in defs.virtualNamespace:
                break
            ns.append(self.tz.token.value)
            tt = self.tz.get()

        self.tz.unget()
        n = self.statement()
        n.namespace = ns
        return n

    publicDefinition = \
    hiddenDefinition = \
    staticDefinition = \
    globalDefinition = \
    finalDefinition = \
    sealedDefinition = \
    overrideDefinition = \
    frozenDefinition = \
    internalDefinition = virtualNamespace

class JooFunction(JSFunction, JooParser):
    """
    Joo function context
    """
    def __init__(self, parent):
        JSFunction.__init__(self, parent)

class JooPack(JooParser):
    """
    Joo package context
    """
    def __init__(self, parent):
        JooParser.__init__(self, parent.tz)
        self.parentContext = parent

# FIXME Extend my! It must handle class prop definitions!
class JooClass(JooParser):
    """
    Joo class context
    """
    def __init__(self, parent):
        JooParser.__init__(self, parent.tz)
        self.parentContext = parent
        self.constructor = None

    def functionDefinition(self, functionForm, requireName=False):
        """
        In a class declaration function has slightly extended syntax. 
        + Returned type. 
        + Rest argument. 
        """
        f = Node(self.tz)

        tt = self.tz.peek() 
        if tt == IDENTIFIER:
            self.tz.get()
            f.name = self.tz.token.value
            if self.tz.token.value == "new":
                self.constructor = f
                f.constructor = True
        elif requireName:
            raise self.tz.newSyntaxError("Missing function identifier")
        elif functionForm == self.EXPRESSED_FORM:
            functionForm = self.LAMBDA_FORM

        f.retType = None
        if self.tz.peek() == IDENTIFIER:
            self.tz.get()
            f.retType = self.tz.token.value

        self.tz.mustMatch(LEFT_PAREN)
        f.params = []
        f.restParams = None
        dots = 0
        while True:
            tt = self.tz.get()
            if f.restParams: break
            if tt == RIGHT_PAREN: break
            if tt == DOT: dots += 1
            elif tt != IDENTIFIER:
                raise self.tz.newSyntaxError("Missing formal parameter")
            # wait for the rest argument: arg, ... rest
            if dots>0: 
                if tt == IDENTIFIER:
                    f.restParams = self.tz.token.value
            else: 
                f.params.append(self.tz.token.value)
                if self.tz.peek() != RIGHT_PAREN:
                    self.tz.mustMatch(COMMA)

        return self.funcRest(f, functionForm)

def parse(source, filename=None, starting_line_number=1):
    """Parse some Jooscript

    Args:
        source: the Jooscript source, as a string
        filename: the filename to include in messages
        starting_line_number: the line number of the first line of the
            passed in source, for output messages
    Returns:
        the parsed source code data structure
    Raises:
        ParseError
    """
    defs_ = dict(
        keywords = defs.jooKeywords,
        tokens = defs.jooTokens,
        fpRegExp = defs.fpRegExp,
        reRegExp = defs.reRegExp,
        opRegExp = defs.opRegExp,
        assignOps = defs.assignOps,
        opTypeNames = defs.opTypeNames
    )
    t = Tokenizer(defs_, source, filename, starting_line_number)
    x = JooParser(t)
    n = x.parse()
    if not t.done:
        raise t.newSyntaxError("Syntax error")
    return n
